﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Utility.NodeGraphs;
using System;
using System.Linq;
using System.Reflection;

namespace Utility.BehaviourTree
{
	public class BTSetting_NodeGraph : TreeNodeGraph
	{
		#region static
		static Dictionary<string, Type> fullnameTypeDict;
		public static IEnumerable<Type> CompositeNodeTypes { get; private set; }
		public static IEnumerable<Type> DecoratorNodeTypes { get; private set; }
		public static IEnumerable<Type> LeafNodeTypes { get; private set; }

		static Dictionary<string, ParameterInfo[]> typeParamsDict = new Dictionary<string, ParameterInfo[]> ();

		static BTSetting_NodeGraph ()
		{
			fullnameTypeDict = (from assembly in AppDomain.CurrentDomain.GetAssemblies ()
								from type in assembly.GetTypes ()
								where type.IsSubclassOf (typeof (BTNode))
								select type)
							   .ToDictionary (type => type.FullName, type => type);

			var createMenuTypes = (from type in fullnameTypeDict.Values
								   where !type.IsAbstract
								   where type.GetCustomAttributes (typeof (HideFromCreateMenuAttribute), false).Length == 0
								   select type)
								  .ToArray ();

			DecoratorNodeTypes = (from type in createMenuTypes
								  where type.IsSubclassOf (typeof (DecoratorNode))
								  select type)
								 .ToArray ();

			CompositeNodeTypes = (from type in createMenuTypes
								  where type.IsSubclassOf (typeof (CompositeNode))
								  select type)
								 .ToArray ();

			LeafNodeTypes = (from type in createMenuTypes
							 where type.IsSubclassOf (typeof (LeafNode))
							 select type)
							.ToArray ();
		}

		public static Type GetNodeType (string fullname)
		{
			Type type = null;
			fullnameTypeDict.TryGetValue (fullname, out type);
			return type;
		}


		public static bool TryGetTypeParams (string typeFullname, out ParameterInfo[] infos)
		{
			infos = null;
			if (!typeParamsDict.ContainsKey (typeFullname))
			{
				Type type = GetNodeType (typeFullname);
				if (type == null)
				{
					return false;
				}
				typeParamsDict[typeFullname] = (from ctor in type.GetConstructors ()
												let parameters = ctor.GetParameters ()
												where parameters.Length > 0
												select parameters)
											   .FirstOrDefault () ?? new ParameterInfo[0];
			}
			infos = typeParamsDict[typeFullname];
			return true;
		}
		#endregion

		public BTRootNode BuildBehaviourTree ()
		{
			if (RootNodeExist)
			{
				return (BTRootNode)BuildNode (RootNode as BTSetting_Node);
			}
			else
			{
				return null;
			}
		}

		BTNode BuildNode (BTSetting_Node nodeSetting)
		{
			BTNode node = CreateNodeFromSetting (nodeSetting);
			if (node == null)
			{
				return null;
			}
			var childSettings = from port in nodeSetting.ChildPorts
								where port.OutputCount > 0
								select port.outputs[0].targetNode as BTSetting_Node;

			foreach (var childSetting in childSettings)
			{
				var childNode = BuildNode (childSetting);
				if (childNode == null)
				{
					continue;
				}
				switch (nodeSetting.nodeType)
				{
					case BTSetting_Node.NodeTypes.Composite:
						(node as CompositeNode).Add (childNode);
						break;
					case BTSetting_Node.NodeTypes.Decorator:
						(node as DecoratorNode).Add (childNode);
						break;
					case BTSetting_Node.NodeTypes.Leaf:
						break;
				}
			}

			return node;
		}

		BTNode CreateNodeFromSetting (BTSetting_Node setting)
		{
			Type type = GetNodeType (setting.typeName);
			if (type == null)
			{
				return null;
			}
			var node = (BTNode)Activator.CreateInstance (type, args: setting.GetArgsArray ());
			node.SetName (setting.nodeName);
			return node;
		}
	}
}